﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Chefbook.DataObjects;
using Chefbook.HelperMethods;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using Chefbook.DataAccess;

namespace Chefbook.ViewModels
{
    /// <summary>
    /// A UI-friendly wrapper for a Category
    /// </summary>
    public class CategoryViewModel : ViewModelBase
    {

        private Category categoryModel;
        private ChefbookXmlRepository repository;
        private CategoryViewModel parent;
        private ObservableCollection<RecipeViewModel> allRecipes = new ObservableCollection<RecipeViewModel>();
        private ObservableCollection<RecipeViewModel> recipes = new ObservableCollection<RecipeViewModel>();
        private ObservableCollection<CategoryViewModel> subCategories = new ObservableCollection<CategoryViewModel>();

        /// <summary>
        /// Creates a new UI-friendly wrapper for a Category to be specified later
        /// </summary>
        public CategoryViewModel() : this(null, ChefbookXmlRepository.Instance) { }

        /// <summary>
        /// Creates a new UI-friendly wrapper for a Category to be specified later
        /// </summary>
        /// <param name="repo">The repository to retrieve categories from</param>
        public CategoryViewModel(ChefbookXmlRepository repo) : this(null, repo) { }

        /// <summary>
        /// Creates a new UI-friendly wrapper for a Category
        /// </summary>
        /// <param name="category">The category to wrap</param>
        public CategoryViewModel(Category category) : this(category, ChefbookXmlRepository.Instance) { }
        
        /// <summary>
        /// Creates a new UI-friendly wrapper for a Category
        /// </summary>
        /// <param name="category">The category to wrap</param>
        /// <param name="repo">The repository to retrieve categories from</param>
        public CategoryViewModel(Category category, ChefbookXmlRepository repo)
        {
            repository = repo;
            this.CategoryModel = category;
        }

        /// <summary>
        /// Gets the wrapped Category
        /// </summary>
        public Category CategoryModel
        {
            get { return categoryModel; }
            set
            {
                if (value == null) return;
                if (categoryModel != null)
                    throw new InvalidOperationException("CategoryModel can only be set once");

                categoryModel = value;
                categoryModel.PropertyChanged += baseCategoryPropertyChanged;
                (categoryModel.Subcategories as INotifyCollectionChanged).CollectionChanged += baseCategorySubCategoryCollectionChanged;
                (categoryModel.DirectRecipes as INotifyCollectionChanged).CollectionChanged += baseCategoryDirectRecipesCollectionChanged;
                (categoryModel.AllRecipes as INotifyCollectionChanged).CollectionChanged += baseCategoryAllRecipesCollectionChanged;

                foreach (var item in categoryModel.DirectRecipes)
                {
                    addRecipeToCollectionSafely(item, recipes);
                }
                foreach (var item in categoryModel.AllRecipes)
                {
                    addRecipeToCollectionSafely(item, allRecipes);
                }
                foreach (var item in categoryModel.Subcategories)
                {
                    addCategoryToCollectionSafely(item, subCategories);
                }
                if (categoryModel.Parent != null)
                { parent = new CategoryViewModel(categoryModel.Parent, repository); }
            }
        }

        /// <summary>
        /// Gets or sets the name of the category
        /// </summary>
        public string Name
        {
            get { return CategoryModel.Name; }
            set { CategoryModel.Name = value; }
        }

        /// <summary>
        /// Gets or sets the parent of the category
        /// </summary>
        public CategoryViewModel Parent
        {
            get { return parent; }
            set { CategoryModel.Parent = value.CategoryModel; }
        }

        bool isSelected;
        /// <summary>
        /// Gets or sets whether this category is selected
        /// </summary>
        public bool IsSelected
        {
            get { return isSelected; }
            set
            {
                if (isSelected != value)
                {
                    isSelected = value;
                    OnPropertyChanged("IsSelected");
                }
            }
        }
        
        bool isExpanded;
        /// <summary>
        /// Gets or sets whether this category is expanded
        /// </summary>
        public bool IsExpanded
        {
            get { return isExpanded; }
            set
            {
                if (isExpanded != value)
                {
                    isExpanded = value;
                    OnPropertyChanged("IsExpanded");
                }
            }
        }

        /// <summary>
        /// Adds a recipe to this category
        /// </summary>
        /// <param name="recipe"></param>
        public void AddRecipe(RecipeViewModel recipe)
        {
            CategoryModel.AddRecipe(recipe.RecipeModel);
        }

        /// <summary>
        /// Removes a recipe from this category
        /// </summary>
        /// <param name="recipe"></param>
        public void RemoveRecipe(RecipeViewModel recipe)
        {
            CategoryModel.RemoveRecipe(recipe.RecipeModel);
        }

        /// <summary>
        /// Adds a subcategory to this categry
        /// </summary>
        /// <param name="sub">The subcategory to add</param>
        public void AddSubcategory(CategoryViewModel sub)
        {
            CategoryModel.AddSubcategory(sub.CategoryModel);
        }

        /// <summary>
        /// Removes a subcategory from this category
        /// </summary>
        /// <param name="sub">The subcategory to remove</param>
        public void RemoveSubcategory(CategoryViewModel sub)
        {
            CategoryModel.RemoveSubcategory(sub.CategoryModel);
        }

        /// <summary>
        /// A read-only collection of all recipes in this category or its subs
        /// </summary>
        public ReadOnlyObservableCollection<RecipeViewModel> AllRecipes
        {
            get { return new ReadOnlyObservableCollection<RecipeViewModel>(allRecipes); }
        }
        
        /// <summary>
        /// A read-only collection of this category's recipes
        /// </summary>
        public ReadOnlyObservableCollection<RecipeViewModel> Recipes
        {
            get { return new ReadOnlyObservableCollection<RecipeViewModel>(recipes); }
        }

        /// <summary>
        /// A read-only collection of this category's subcategories
        /// </summary>
        public ReadOnlyObservableCollection<CategoryViewModel> Subcategories
        {
            get { return new ReadOnlyObservableCollection<CategoryViewModel>(subCategories); }
        }

        /// <summary>
        /// Returns whether this category had value equality with another
        /// </summary>
        /// <param name="categoryToCompare">The category to compare to</param>
        /// <returns>True if they wrap the same object, else false</returns>
        public bool ValueEquals(CategoryViewModel categoryToCompare)
        {
            return CategoryModel.ValueEquals(categoryToCompare.CategoryModel);
        }

        /// <summary>
        /// Add a recipe to the a collection and avoid the infinite constructor loop
        /// </summary>
        private void addRecipeToCollectionSafely(Recipe recipe, ICollection<RecipeViewModel> collection)
        {
            if (repository.Recipes.ContainsKey(recipe))
            {
                collection.AddAndThrowIfNullOrNotNew(repository.Recipes[recipe]);
            }
            else
            {
                var recipeVM = new RecipeViewModel(repository);
                repository.Recipes[recipe] = recipeVM;
                recipeVM.RecipeModel = recipe;
                collection.AddAndThrowIfNullOrNotNew(recipeVM);
            }
        }
        private void addCategoryToCollectionSafely(Category category, ObservableCollection<CategoryViewModel> collection)
        {
            if (repository.Categories.ContainsKey(category))
            {
                collection.AddAndThrowIfNullOrNotNew(repository.Categories[category]);
            }
            else
            {
                var categoryVM = new CategoryViewModel(repository);
                repository.Categories[category] = categoryVM;
                categoryVM.CategoryModel = category;
                collection.AddAndThrowIfNullOrNotNew(categoryVM);
            }
        }

        private void baseCategorySubCategoryCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    subCategories.Add(new CategoryViewModel((Category)e.NewItems[0], repository));
                    break;
                case NotifyCollectionChangedAction.Remove:
                    subCategories.Remove(new CategoryViewModel((Category)e.OldItems[0], repository));
                    break;
                default:
                    throw new NotImplementedException();
            }
        }

        private void baseCategoryDirectRecipesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    addRecipeToCollectionSafely((Recipe)e.NewItems[0], recipes);
                    break;
                case NotifyCollectionChangedAction.Remove:
                    recipes.Remove(new RecipeViewModel((Recipe)e.OldItems[0], repository));
                    break;
                default:
                    throw new NotImplementedException();
            }
        }

        private void baseCategoryAllRecipesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    addRecipeToCollectionSafely((Recipe)e.NewItems[0],allRecipes);
                    break;
                case NotifyCollectionChangedAction.Remove:
                    allRecipes.Remove(new RecipeViewModel((Recipe)e.OldItems[0], repository));
                    break;
                default:
                    throw new NotImplementedException();
            }
        }

        private void baseCategoryPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            switch (e.PropertyName)
            {
                case "Name":
                    OnPropertyChanged("Name");
                    break;
                case "Parent":
                    if (CategoryModel.Parent == null)
                    { parent = null; }
                    else
                    { parent = new CategoryViewModel(CategoryModel.Parent, repository); }
                    OnPropertyChanged("Parent");
                    break;
                default:
                    throw new NotImplementedException();
            }
        }

        /// <summary>
        /// Compares whether this object wraps the same Category as the parameter
        /// </summary>
        /// <param name="obj">The object to compare equality with</param>
        /// <returns>True if they wrap the same Category</returns>
        public override bool Equals(object obj)
        {
            var comparedCat = obj as CategoryViewModel;
            if(comparedCat == null)
            {return false;}
            return CategoryModel == comparedCat.CategoryModel;
        }

        /// <summary>
        /// Gets the hashcode of the wrapped Categry
        /// </summary>
        /// <returns>The hashcode</returns>
        public override int GetHashCode()
        {
            return CategoryModel.GetHashCode();
        }
    }
}
